<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebRTC视频通话</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 0;
      padding: 20px;
      background-color: #f5f5f5;
    }
    .container {
      max-width: 1200px;
      margin: 0 auto;
    }
    h1 {
      text-align: center;
      color: #333;
    }
    .room-controls {
      margin: 20px 0;
      text-align: center;
    }
    input, button {
      padding: 10px;
      margin: 5px;
      font-size: 16px;
    }
    button {
      background-color: #4CAF50;
      color: white;
      border: none;
      cursor: pointer;
      border-radius: 4px;
    }
    button:hover {
      background-color: #45a049;
    }
    .video-container {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: 20px;
      margin-top: 20px;
    }
    .video-item {
      position: relative;
      width: 320px;
      background-color: #000;
      border-radius: 8px;
      overflow: hidden;
    }
    .video-item video {
      width: 100%;
      height: 240px;
      object-fit: cover;
    }
    .video-label {
      position: absolute;
      bottom: 10px;
      left: 10px;
      background-color: rgba(0, 0, 0, 0.5);
      color: white;
      padding: 5px 10px;
      border-radius: 4px;
    }
    .status {
      text-align: center;
      margin: 10px 0;
      font-weight: bold;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>WebRTC视频通话</h1>
    
    <div class="room-controls">
      <input type="text" id="roomId" placeholder="输入房间ID">
      <button id="joinBtn">加入房间</button>
    </div>
    
    <div class="status" id="status">未连接</div>
    
    <div class="video-container">
      <div class="video-item">
        <video id="localVideo" autoplay muted playsinline></video>
        <div class="video-label">我</div>
      </div>
      <div id="remoteVideos"></div>
    </div>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    // DOM元素
    const joinBtn = document.getElementById('joinBtn');
    const roomIdInput = document.getElementById('roomId');
    const statusEl = document.getElementById('status');
    const localVideo = document.getElementById('localVideo');
    const remoteVideos = document.getElementById('remoteVideos');
    
    // WebRTC配置
    const configuration = {
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        { urls: 'stun:stun1.l.google.com:19302' }
      ]
    };
    
    // 变量
    let socket;
    let localStream;
    let roomId;
    const peerConnections = {};
    
    // 初始化
    async function init() {
      try {
        try {
          // 先尝试同时获取视频和音频
          localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
        } catch (mediaError) {
          if (mediaError.name === 'NotFoundError') {
            // 如果找不到摄像头，尝试只使用音频
            localStream = await navigator.mediaDevices.getUserMedia({ audio: true });
            
            // 创建一个画布作为视频占位符
            const canvas = document.createElement('canvas');
            canvas.width = 640;
            canvas.height = 480;
            const ctx = canvas.getContext('2d');
            
            // 绘制提示信息
            ctx.fillStyle = '#333';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = '#fff';
            ctx.font = '24px Arial';
            ctx.textAlign = 'center';
            ctx.fillText('未检测到摄像头设备', canvas.width/2, canvas.height/2 - 20);
            ctx.font = '16px Arial';
            ctx.fillText('仅使用音频通话', canvas.width/2, canvas.height/2 + 20);
            
            // 从画布创建视频流
            const videoStream = canvas.captureStream(30);
            localStream = new MediaStream([
              ...videoStream.getVideoTracks(),
              ...localStream.getAudioTracks()
            ]);
            
            statusEl.textContent = '警告: 未检测到摄像头，将仅使用音频通话';
          } else {
            throw mediaError;
          }
        }
        
        localVideo.srcObject = localStream;
        
        // 连接Socket.io服务器
        socket = io('http://localhost:3002');
        setupSocketEvents();
        
        if (!statusEl.textContent.includes('警告')) {
          statusEl.textContent = '已连接到服务器';
        }
      } catch (error) {
        console.error('初始化错误:', error);
        if (error.name === 'NotAllowedError') {
          statusEl.textContent = '错误: 请允许访问摄像头和麦克风权限';
        } else if (error.name === 'NotFoundError') {
          statusEl.textContent = '错误: 未找到任何媒体设备（摄像头/麦克风）';
        } else {
          statusEl.textContent = `错误: ${error.message}`;
        }
        joinBtn.disabled = true;
      }
    }
    
    // 设置Socket.io事件
    function setupSocketEvents() {
      // 连接事件
      socket.on('connect', () => {
        console.log('已连接到信令服务器');
      });
      
      // 断开连接事件
      socket.on('disconnect', () => {
        console.log('与信令服务器断开连接');
        statusEl.textContent = '已断开连接';
      });
      
      // 用户加入事件
      socket.on('user-joined', (userId) => {
        console.log(`用户加入: ${userId}`);
        createPeerConnection(userId);
        // 向新用户发送offer
        createOffer(userId);
      });
      
      // 用户离开事件
      socket.on('user-left', (userId) => {
        console.log(`用户离开: ${userId}`);
        if (peerConnections[userId]) {
          peerConnections[userId].close();
          delete peerConnections[userId];
        }
        // 移除视频元素
        const videoEl = document.getElementById(`video-${userId}`);
        if (videoEl) {
          videoEl.parentElement.remove();
        }
      });
      
      // 房间用户列表事件
      socket.on('room-users', (users) => {
        console.log('房间用户:', users);
        // 为房间中的每个用户创建对等连接
        users.forEach(userId => {
          createPeerConnection(userId);
        });
      });
      
      // 接收offer事件
      socket.on('offer', async ({ senderId, description }) => {
        console.log(`收到来自 ${senderId} 的offer`);
        // 确保有对等连接
        if (!peerConnections[senderId]) {
          createPeerConnection(senderId);
        }
        
        const pc = peerConnections[senderId];
        // 设置远程描述
        await pc.setRemoteDescription(new RTCSessionDescription(description));
        // 创建answer
        const answer = await pc.createAnswer();
        await pc.setLocalDescription(answer);
        
        // 发送answer
        socket.emit('answer', {
          targetId: senderId,
          description: pc.localDescription
        });
      });
      
      // 接收answer事件
      socket.on('answer', async ({ senderId, description }) => {
        console.log(`收到来自 ${senderId} 的answer`);
        const pc = peerConnections[senderId];
        if (pc) {
          await pc.setRemoteDescription(new RTCSessionDescription(description));
        }
      });
      
      // 接收ICE候选事件
      socket.on('ice-candidate', async ({ senderId, candidate }) => {
        console.log(`收到来自 ${senderId} 的ICE候选`);
        const pc = peerConnections[senderId];
        if (pc) {
          await pc.addIceCandidate(new RTCIceCandidate(candidate));
        }
      });
    }
    
    // 创建对等连接
    function createPeerConnection(userId) {
      if (peerConnections[userId]) return;
      
      const pc = new RTCPeerConnection(configuration);
      peerConnections[userId] = pc;
      
      // 添加本地流
      localStream.getTracks().forEach(track => {
        pc.addTrack(track, localStream);
      });
      
      // ICE候选事件
      pc.onicecandidate = (event) => {
        if (event.candidate) {
          socket.emit('ice-candidate', {
            targetId: userId,
            candidate: event.candidate
          });
        }
      };
      
      // 接收远程流
      pc.ontrack = (event) => {
        // 检查是否已经有这个用户的视频元素
        let videoEl = document.getElementById(`video-${userId}`);
        if (!videoEl) {
          // 创建新的视频容器和元素
          const videoItem = document.createElement('div');
          videoItem.className = 'video-item';
          
          videoEl = document.createElement('video');
          videoEl.id = `video-${userId}`;
          videoEl.autoplay = true;
          videoEl.playsinline = true;
          
          const label = document.createElement('div');
          label.className = 'video-label';
          label.textContent = `用户 ${userId.substr(0, 5)}...`;
          
          videoItem.appendChild(videoEl);
          videoItem.appendChild(label);
          remoteVideos.appendChild(videoItem);
        }
        
        // 设置视频源
        if (videoEl.srcObject !== event.streams[0]) {
          videoEl.srcObject = event.streams[0];
        }
      };
      
      return pc;
    }
    
    // 创建并发送offer
    async function createOffer(userId) {
      const pc = peerConnections[userId];
      if (!pc) return;
      
      try {
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);
        
        socket.emit('offer', {
          targetId: userId,
          description: pc.localDescription
        });
      } catch (error) {
        console.error('创建offer错误:', error);
      }
    }
    
    // 加入房间
    function joinRoom() {
      roomId = roomIdInput.value.trim();
      if (!roomId) {
        alert('请输入房间ID');
        return;
      }
      
      socket.emit('join-room', roomId);
      statusEl.textContent = `已加入房间: ${roomId}`;
      joinBtn.disabled = true;
      roomIdInput.disabled = true;
    }
    
    // 事件监听
    joinBtn.addEventListener('click', joinRoom);
    
    // 初始化应用
    init();
  </script>
</body>
</html>